﻿using Sustainalytics.Entities.ProductInvolvement;
using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver.Linq;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.ClientDW.Entities;
using MongoDB.Bson;
using MongoDB.Driver;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Utils;
using System.Text.RegularExpressions;
using System.Linq.Expressions;
using static Sustainalytics.Utils.AssessementList;

namespace Sustainalytics.DataLayer.PI
{
    public class ProductInvolvementDataRepository : RepositoryCollection<ProductInvolvementData>, IProductInvolvementDataRepository
    {
        private readonly string _clientDWConnectionString;
        private readonly string _picnxString;
        private readonly Dictionary<string, double[]> _selectedRanges = new Dictionary<string, double[]>();

        private readonly Dictionary<string, double[]> _intervals;

        public ProductInvolvementDataRepository(string picnxString, string clientDWConnectionString)
            : base(picnxString)
        {
            _clientDWConnectionString = clientDWConnectionString;
            _picnxString = picnxString;

            _intervals = new Dictionary<string, double[]>()
            {
                { "0-4.9%", new double[] { 0, 4.9 } },
                { "5-9.9%", new double[] { 5, 9.9 } },
                { "10-24.9%", new double[] { 10, 24.9 } },
                { "25-49.9%", new double[] { 25, 49.9 } },
                { "50-100%", new double[] { 50, 100 } }
            };

        }

        public ProductInvolvementData ReadById(Guid id)
        {
            return base.Read(id);
        }


        //Replace with researc state
        public ProductInvolvementData ReadById(List<int> userUniverseIds, List<string> claimedIndicators, Guid id, ResearchState researchState)
        {
            id = HasPiResearch(id);

            if (id == Guid.Empty)
                throw new PICompanyAssessmentNotFoundException_NotResearched();

            var assessment = base.Read(id);

            var corporatedatauniverses = MongoFactory.GetCollectionFromDatabase<CorporateData>(_clientDWConnectionString)
                .Find(cd => cd.Id == id).FirstOrDefault().UniverseIds;

            if (assessment != null)
            {
                if (corporatedatauniverses != null && corporatedatauniverses.ContainsAny(userUniverseIds))
                    return assessment;
                else
                    throw new PICompanyAssessmentNotInUniversesException();
            }
            else
            {
                if (researchState == ResearchState.FrameworkNotApplicable)
                    throw new PICompanyAssessmentNotFoundException_FrameworkNotApplicable();

                if (researchState != ResearchState.ResearchAvailable)
                    throw new PICompanyAssessmentNotFoundException_NotResearched();

                if (researchState == ResearchState.ResearchAvailable)
                    throw new PICompanyAssessmentNotFoundException_ResearchedWithNoData();
            }

            return assessment;
        }

        public ProductInvolvementData ReadByCompanyId(int id)
        {
            try
            {
                var query = Builders<ProductInvolvementData>.Filter.Eq(x => x.CompanyId, HasPiResearch(id));

                var result = Collection().Find(query).SingleOrDefault();

                RepositoryLog.Log("FindOneAs(CapitalIQID)", id, result ?? default(ProductInvolvementData), GetDatabaseName(), GetCollectionName());

                return result;
            }
            catch (Exception ex)
            {
                RepositoryLog.Log("FindOneAs(CapitalIQID)", id, ex, GetDatabaseName(), GetCollectionName());
                throw;
            }
        }

        public new IEnumerable<ProductInvolvementData> ReadAll()
        {
            return base.ReadAll();
        }

        public IEnumerable<ProductInvolvementData> Read(int? skip, int? take, string sort, bool sortascending)
        {
            return base.ReadAll(null, skip, take);
        }

        public void Create(IEnumerable<ProductInvolvementData> companies)
        {
            foreach (var c in companies)
                base.Create(c);
        }

        public IEnumerable<ProductInvolvementData> ReadByIdNames(List<string> names)
        {
            throw new NotImplementedException();
        }

        public void Update(IEnumerable<ProductInvolvementData> companies)
        {
            foreach (var c in companies)
                base.Update(c.Id, c);
        }

        public void Update(ProductInvolvementData company)
        {
            base.Update(company.Id, company);
        }

        public void Delete(ProductInvolvementData company)
        {
            base.Delete(company.Id);
        }

        public long ReadCount(ProductInvolvementDataListFilters filters, List<int> userUniverseIds, List<string> claimedIndicators)
        {
            var filter = GetFilterDefinition(filters, userUniverseIds, claimedIndicators);

            var result = Collection().Aggregate()
                .Unwind<ProductInvolvementData, ProductInvolvementDataUnwinded>(x => x.Indicators)
                .Match(Builders<ProductInvolvementDataUnwinded>.Filter.And(filter))
                .Group(x => x.Id, g => new ProductInvolvementData()
                {
                    Id = g.First().Id,
                })
                .Group(new BsonDocument
                {
                    {"_id", "_id" },
                    { "Count", new BsonDocument("$sum", 1) }
                });

            return result.Any() ? result.First()["Count"].AsInt32 : 0;
        }

        public List<ProductInvolvementDataIndicator> GetPiIndicatorCompanyIdAndIndicatorSymbol(Guid companyId)
        {
            var id = HasPiResearch(companyId);

            return Collection()
                    .Find(Builders<ProductInvolvementData>.Filter.Eq(p => p.Id, id))
                    .Project<ProductInvolvementDataIndicator>(Builders<ProductInvolvementData>.Projection
                        .Include(p => p.Indicators))
                    .ToList();
        }

        public IEnumerable<string> GetPiIndicators()
        {
            return MongoFactory.GetCollectionFromDatabase<ProductInvolvementFramework>(MongoFactory.GetConnectionStrings().ProductInvolvementDW)
                    .Find(_ => true)
                    .Project<ProductInvolvementFramework>(Builders<ProductInvolvementFramework>.Projection
                        .Include(p => p.Symbol))
                    .ToList()
                    .Select(x => x.Symbol.ToLower())
                    .Distinct();
        }

        public IEnumerable<ProductInvolvementFramework> GetFrameworks()
        {
            return MongoFactory.GetCollectionFromDatabase<ProductInvolvementFramework>(MongoFactory.GetConnectionStrings().ProductInvolvementDW)
                   .Find(_ => true)
                   .ToList();
        }

        public IEnumerable<object> GetIndustriesFromCorporateData()
        {
            return MongoFactory.GetCollectionFromDatabase<CorporateData>(MongoFactory.GetConnectionStrings().ClientDW)
                .AsQueryable().Select(x => new
                {
                    Id = x.SubPeerGroupId,
                    Name = x.SubPeerGroup
                }).Distinct().OrderBy(x=>x.Name);
        }

        public List<ProductInvolvementData> GetPiCompanyAssessmentByCompanyIds(List<Guid> companyIds)
        {
            companyIds = HasPiResearch();

            var query = new List<FilterDefinition<ProductInvolvementData>> {
                Builders<ProductInvolvementData>.Filter.In(x=> x.Id, new BsonArray(companyIds)),
                Builders<ProductInvolvementData>.Filter.SizeGt(x=> x.Indicators, 0)
            };

            return Collection().Find(Builders<ProductInvolvementData>.Filter.And(query))
                        .Project<ProductInvolvementData>(Builders<ProductInvolvementData>.Projection.Include(comp => comp.Indicators))
                        .ToList();
        }

        private Guid HasPiResearch(Guid companyId)
        {
            var id = MongoFactory.GetCollectionFromDatabase<CorporateData>(_clientDWConnectionString)
                .Find(cd => cd.Id == companyId
                        && cd.ResearchParentId == null
                        && cd.ProductInvolvementResearchState.ResearchState == ResearchState.ResearchAvailable
                        && cd.ProductInvolvementResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                .Project(cd => cd.Id)
                .SingleOrDefault();

            return id;
        }

        private int HasPiResearch(int cIqId)
        {
            return MongoFactory.GetCollectionFromDatabase<CorporateData>(_clientDWConnectionString)
                .Find(cd => cd.CapitalIqId == cIqId
                        && cd.ResearchParentId == null
                        && cd.ProductInvolvementResearchState.ResearchState == ResearchState.ResearchAvailable
                        && cd.ProductInvolvementResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                .Project(cd => cd.CapitalIqId)
                .SingleOrDefault();
        }

        private List<Guid> HasPiResearch()
        {
            var companyIds = MongoFactory.GetCollectionFromDatabase<CorporateData>(_clientDWConnectionString)
                .Find(cd => cd.ResearchParentId == null
                         && cd.ProductInvolvementResearchState.ResearchState == ResearchState.ResearchAvailable
                         && cd.ProductInvolvementResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                .Project(cd => cd.Id)
                .ToList();

            return companyIds;
        }

        private List<FilterDefinition<ProductInvolvementDataUnwinded>> GetFilterDefinition(ProductInvolvementDataListFilters filters, List<int> userUniverseIds, List<string> claimedIndicators)
        {
            //filters.RangeOwnerships = new List<string>();
            //filters.RangeOwnerships.Add("0-4.9%");
            //filters.RangeOwnerships.Add("25-49.9%");
            //filters.RangeOwnerships.Add("N/A");

            var companyIds = HasPiResearch(null, userUniverseIds);

            if (!companyIds.Any())
            {
                throw new PICompanyAssessmentShortReportEmptyNoUniversesException();
            }

            var filter = new List<FilterDefinition<ProductInvolvementDataUnwinded>>(){
                Builders<ProductInvolvementDataUnwinded>.Filter.In(el => el.Id, companyIds)
            };

            filter.Add(Builders<ProductInvolvementDataUnwinded>.Filter.Where(x => claimedIndicators.Contains(x.Indicators.IndicatorSymbol)));

            if (!filter.Any())
            {
                throw new PICompanyAssessmentShortReportEmptyNoClaimsException();
            }

            if (!string.IsNullOrEmpty(filters.CompanyNameLowerCase))
            {
                var regexFilter = new BsonRegularExpression(Regex.Escape(filters.CompanyNameLowerCase), "i");
                filter.Add(Builders<ProductInvolvementDataUnwinded>.Filter.Regex(x => x.CompanyNameLowerCase, regexFilter));
            }

            if (!filters.Indicators.IsNullOrEmpty())
            {
                filter.Add(Builders<ProductInvolvementDataUnwinded>.Filter.Where(x => filters.Indicators.Contains(x.Indicators.IndicatorSymbol)));
            }

            if (!filters.Subindustries.IsNullOrEmpty())
            {
                filter.Add(Builders<ProductInvolvementDataUnwinded>.Filter.Where(x => filters.Subindustries.Contains(x.IndustryId)));
            }

            if (!filters.RangeOwnerships.IsNullOrEmpty())
            {

                Expression<Func<ProductInvolvementDataUnwinded, bool>> predicateRangeWithOwnership = c => true;
                bool initial = true;
                foreach (string range in filters.RangeOwnerships)
                {
                    if (initial)
                    {
                        predicateRangeWithOwnership = x => x.Indicators.RangeOwnership == range;
                        initial = false;
                    }
                    else
                    {
                        predicateRangeWithOwnership = predicateRangeWithOwnership.Or(x => x.Indicators.RangeOwnership == range);
                    }

                    var findinterval = _intervals.Where(a => a.Key == range);
                    if (!findinterval.IsNullOrEmpty())
                    {
                        var rg = findinterval.FirstOrDefault();
                        predicateRangeWithOwnership = predicateRangeWithOwnership.Or(y => y.Indicators.OwnershipPercent >= rg.Value[0] &&
                                 y.Indicators.OwnershipPercent <= rg.Value[1]);
                        _selectedRanges.Add(rg.Key, new double[] { rg.Value[0], rg.Value[1] });
                    }
                }

                filter.Add(Builders<ProductInvolvementDataUnwinded>.Filter.Where(predicateRangeWithOwnership));
            }

            return filter;
        }

        private List<Guid> HasPiResearch(IEnumerable<Guid> companyIds, List<int> universeIds)
        {
            List<Guid> ids;

            if (companyIds != null)
            {
                ids = MongoFactory.GetCollectionFromDatabase<CorporateData>(_clientDWConnectionString)
                .Find(cd => companyIds.Contains(cd.Id)
                         && cd.ResearchParentId == null
                         && cd.ProductInvolvementResearchState.ResearchState == ResearchState.ResearchAvailable
                         && cd.ProductInvolvementResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                .Project(cd => cd.Id)
                .ToList();
            }
            else
            {
                ids = MongoFactory.GetCollectionFromDatabase<CorporateData>(_clientDWConnectionString)
                .Find(cd => cd.ResearchParentId == null
                         && cd.UniverseIds.Any(x => universeIds.Contains(x))
                         && cd.ProductInvolvementResearchState.ResearchState == ResearchState.ResearchAvailable
                         && cd.ProductInvolvementResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                .Project(cd => cd.Id)
                .ToList();
            }

            return ids;
        }

        public IEnumerable<CompanyList> Read(ProductInvolvementDataListFilters filters,
            List<int> userUniverseIds,
            List<string> claimedIndicators,
            int? skip, int? take, string[] orderBy = null, bool orderByAsc = true)
        {
            var filter = GetFilterDefinition(filters, userUniverseIds, claimedIndicators);

            var cursor = Collection()
                .Aggregate()
                .Unwind<ProductInvolvementData, ProductInvolvementDataUnwinded>(x => x.Indicators)
                .Match(Builders<ProductInvolvementDataUnwinded>.Filter.And(filter))
                .Group(x => x.Id, g => new ProductInvolvementData()
                {
                    CompanyId = g.First().CompanyId,
                    Indicators = g.Select(x => x.Indicators).ToList(),
                    CompanyName = g.First().CompanyName
                });

            var pidata = new List<ProductInvolvementData>();

            var companyIdsToMatch = cursor.ToList().Select(x => x.CompanyId);

            if (orderBy != null && orderBy.Length > 0)
            {
                var sortBy = new List<SortDefinition<ProductInvolvementData>>();
                var firstOrderColumn = string.Empty;

                foreach (var orderByItem in orderBy)
                {
                    if (orderByItem != "CompanyNameLowerCase")
                    {
                        firstOrderColumn = orderByItem;
                        sortBy.Add(orderByAsc ?
                            Builders<ProductInvolvementData>.Sort.Ascending(orderByItem) :
                            Builders<ProductInvolvementData>.Sort.Descending(orderByItem));
                    }
                }

                if (firstOrderColumn != "CompanyNameLowerCase")
                {
                    sortBy.Add(Builders<ProductInvolvementData>.Sort.Ascending("CompanyNameLowerCase"));
                }
                else
                {
                    sortBy.Add(orderByAsc ?
                           Builders<ProductInvolvementData>.Sort.Ascending("CompanyNameLowerCase") :
                           Builders<ProductInvolvementData>.Sort.Descending("CompanyNameLowerCase"));
                }

                var picompanyids = Collection().Aggregate()
               .Unwind<ProductInvolvementData, ProductInvolvementDataUnwinded>(x => x.Indicators)
               .Match(Builders<ProductInvolvementDataUnwinded>.Filter.And(
                      Builders<ProductInvolvementDataUnwinded>.Filter.In(pi => pi.CompanyId, companyIdsToMatch),
                      Builders<ProductInvolvementDataUnwinded>.Filter.And(filter)))
               .Group(x => x.Id, g => new ProductInvolvementData()
               {
                   CompanyId = g.First().CompanyId,
                   Indicators = g.Select(x => x.Indicators).ToList(),
                   CompanyName = g.First().CompanyName,
                   CompanyNameLowerCase = g.First().CompanyNameLowerCase
               })
               .Sort(Builders<ProductInvolvementData>.Sort.Combine(sortBy))
               .ToList()
               .Select(pi => pi.CompanyId);

                if (skip.HasValue)
                {
                    picompanyids = picompanyids.Skip(skip.Value).ToList();
                }

                if (take.HasValue)
                {
                    picompanyids = picompanyids.Take(take.Value);
                }

                var corporateData = MongoFactory.GetCollectionFromDatabase<CorporateData>(_clientDWConnectionString)
                    .Find(Builders<CorporateData>.Filter.In(cd => cd.CapitalIqId, picompanyids.ToList()))
                    .Project<CorporateData>(Builders<CorporateData>.Projection
                    .Include(x => x.SubPeerGroup)
                    .Include(x => x.MainExchange)
                    .Include(x => x.MainTicker)
                    .Include(x => x.CapitalIqId))
                    .ToList()
                    .Select(x => new
                    {
                        x.Id,
                        x.CapitalIqId,
                        x.SubPeerGroup,
                        x.MainExchange,
                        x.MainTicker
                    });

                pidata = Collection()
               .Aggregate()
               .Unwind<ProductInvolvementData, ProductInvolvementDataUnwinded>(x => x.Indicators)
               .Match(Builders<ProductInvolvementDataUnwinded>.Filter.In(cd => cd.CompanyId, picompanyids))
               .Match(Builders<ProductInvolvementDataUnwinded>.Filter.And(filter))
               .Group(x => x.Id, g => new ProductInvolvementData()
               {
                   CompanyId = g.First().CompanyId,
                   Indicators = g.Select(x => x.Indicators).ToList(),
                   CompanyName = g.First().CompanyName,
                   CompanyNameSafeUrl = g.First().CompanyNameSafeUrl,
                   CompanyNameLowerCase = g.First().CompanyNameLowerCase
               })
               .Sort(Builders<ProductInvolvementData>.Sort.Combine(sortBy))
               .ToList();

                for (var i = 0; i < corporateData.Count(); i++)
                {
                    var match = corporateData.FirstOrDefault(x => x.CapitalIqId == pidata[i].CompanyId);

                    if (match != null)
                    {
                        pidata[i].Industry = match.SubPeerGroup;
                        pidata[i].Exchange = match.MainExchange;
                        pidata[i].Ticker = match.MainTicker;
                    }
                }
            }

            var piIndicators = MongoFactory.GetCollectionFromDatabase<ProductInvolvementFramework>(_picnxString)
                .Find(_ => true)
                        .Project<ProductInvolvementFramework>(Builders<ProductInvolvementFramework>
                        .Projection
                        .Include(x => x.Name)
                        .Include(x => x.Categories)
                        .Include(x => x.Symbol)).ToList();

            var piIndicatorsDictionary = piIndicators.ToDictionary(x => x.Symbol, x => x);

            var computedList = pidata.Select(comp =>
            {
                IList<PIIndicators> indicators = new List<PIIndicators>();
                comp.Indicators.ForEach(ind =>
                {
                    if ((filters.Indicators.IsNullOrEmpty() || filters.Indicators.Contains(ind.IndicatorSymbol))
                    //&& (filters.RangeOwnerships.IsNullOrEmpty() ||
                    //(_selectedRanges.Any() && _selectedRanges.Keys.Contains(ind.RangeOwnership)
                    //|| _selectedRanges.Any(a => ind.OwnershipPercent >= a.Value[0] && ind.OwnershipPercent <= a.Value[1]))
                    //    )
                    )
                    {
                        var indicatorFramework = piIndicatorsDictionary[ind.IndicatorSymbol];
                        var categoryFramework = indicatorFramework.Categories.FirstOrDefault(cat => cat.Symbol == ind.CategorySymbol);

                        var currentIndicator = indicators.FirstOrDefault(x => x.Symbol == ind.IndicatorSymbol);
                        if (currentIndicator != null)
                        {
                            currentIndicator.Categories.Add(new PICategory(categoryFramework.Symbol, categoryFramework.CategoryOfInvolvement, ind.LevelOfInvolvement.Text, ind.RangeOwnership));
                            currentIndicator.Categories = currentIndicator.Categories.OrderBy(x => x.Symbol).ToList();
                        }
                        else
                        {
                            if (claimedIndicators.Contains(ind.IndicatorSymbol))
                                indicators.Add(new PIIndicators()
                                {
                                    Name = indicatorFramework.Name,
                                    Categories = new List<PICategory>() { new PICategory(categoryFramework.Symbol, categoryFramework.CategoryOfInvolvement, ind.LevelOfInvolvement.Text, ind.RangeOwnership) },
                                    Symbol = ind.IndicatorSymbol
                                });
                        }
                    }
                });

                return new CompanyList
                {
                    Id = comp.Id,
                    CompanyName = comp.CompanyName,
                    CompanyNameSafeUrl = comp.CompanyNameSafeUrl,
                    Exchange = comp.Exchange,
                    Ticker = comp.Ticker,
                    Industry = comp.Industry,
                    Indicators = indicators?.OrderBy(x => x.Name)
                };
            });

            return computedList;
        }

        public List<double> ManageRange(string range)
        {
            List<double> result = new List<double>();
            if (range.Contains("-"))
            {
                var ranges = range.Remove('%').Split('-');

                foreach (var item in ranges)
                {
                    result.Add(double.Parse(item));
                }
            }
            else
            {
                result.Add(double.Parse(range.Remove('%')));
            }
            return result;
        }
    }
}

